/* -------------------------------------------------------------------------
 *
 * ipc_test.c
 *     Simplistic testbed for shared memory and semaphore code.
 *
 * This file allows for quick "smoke testing" of a PG semaphore or shared
 * memory implementation, with less overhead than compiling up a whole
 * installation.  To use:
 *  1. Run configure, then edit src/include/pg_config.h to select the
 *     USE_xxx_SEMAPHORES and USE_xxx_SHARED_MEMORY settings you want.
 *     Also, adjust the pg_sema.c and pg_shmem.c symlinks in
 *     src/backend/port/ if needed.
 *  2. In src/backend/port/, do "gmake ipc_test".
 *  3. Run ipc_test and see if it works.
 *  4. If it seems to work, try building the whole system and running
 *   the parallel regression tests for a more complete test.
 *
 *
 * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *    src/common/backend/port/ipc_test.c
 *
 * -------------------------------------------------------------------------
 */
#include "postgres.h"
#include "knl/knl_variable.h"


#include "miscadmin.h"
#include "storage/ipc.h"
#include "storage/lock/pg_sema.h"
#include "storage/pg_shmem.h"

/* stuff needed to satisfy references in shmem/sema code */
volatile bool InterruptPending = false;
volatile bool QueryCancelPending = false;
volatile bool ProcDiePending = false;
volatile bool ImmediateInterruptOK = false;
volatile uint32 InterruptHoldoffCount = 0;
volatile uint32 CritSectionCount = 0;

bool IsUnderPostmaster = false;
bool assert_enabled = true;

int g_instance.shmem_cxt.MaxBackends = 32;
int NBuffers = 64;

char* DataDir = ".";

#define MAX_ON_EXITS 20

static struct ONEXIT {
    pg_on_exit_callback function;
    Datum arg;
} on_proc_exit_list[MAX_ON_EXITS], on_shmem_exit_list[MAX_ON_EXITS];

static int on_proc_exit_index, on_shmem_exit_index;

void proc_exit(int code)
{
    shmem_exit(code);
    while (--on_proc_exit_index >= 0)
        (*on_proc_exit_list[on_proc_exit_index].function)(code, on_proc_exit_list[on_proc_exit_index].arg);
    exit(code);
}

void shmem_exit(int code)
{
    while (--on_shmem_exit_index >= 0)
        (*on_shmem_exit_list[on_shmem_exit_index].function)(code, on_shmem_exit_list[on_shmem_exit_index].arg);
    on_shmem_exit_index = 0;
}

void on_shmem_exit(pg_on_exit_callback function, Datum arg)
{
    if (on_shmem_exit_index >= MAX_ON_EXITS)
        ereport(FATAL, (errmsg("out of on_shmem_exit slots")));

    on_shmem_exit_list[on_shmem_exit_index].function = function;
    on_shmem_exit_list[on_shmem_exit_index].arg = arg;

    ++on_shmem_exit_index;
}

void on_exit_reset(void)
{
    on_shmem_exit_index = 0;
    on_proc_exit_index = 0;
}

void AddToDataDirLockFile(int target_line, const char* str)
{}

void ProcessInterrupts(void)
{}

void ExceptionalCondition(const char* conditionName, const char* errorType, const char* fileName, int lineNumber)
{
    fprintf(stderr, "TRAP: %s(\"%s\", File: \"%s\", Line: %d)\n", errorType, conditionName, fileName, lineNumber);
    abort();
}

int errcode_for_file_access(void)
{
    return 0;
}

bool errstart(int elevel, const char* filename, int lineno, const char* funcname, const char* domain)
{
    return (elevel >= ERROR);
}

void errfinish(int dummy, ...)
{
    proc_exit(1);
}

void elog_start(const char* filename, int lineno, const char* funcname)
{}

void elog_finish(int elevel, const char* fmt, ...)
{
    fprintf(stderr, "ERROR: %s\n", fmt);
    proc_exit(1);
}

int errcode(int sqlerrcode)
{
    return 0; /* return value does not matter */
}

int errmsg(const char* fmt, ...)
{
    fprintf(stderr, "ERROR: %s\n", fmt);
    return 0; /* return value does not matter */
}

int errmsg_internal(const char* fmt, ...)
{
    fprintf(stderr, "ERROR: %s\n", fmt);
    return 0; /* return value does not matter */
}

int errdetail(const char* fmt, ...)
{
    fprintf(stderr, "DETAIL: %s\n", fmt);
    return 0; /* return value does not matter */
}

int errdetail_log(const char* fmt, ...)
{
    fprintf(stderr, "DETAIL: %s\n", fmt);
    return 0; /* return value does not matter */
}

int errhint(const char* fmt, ...)
{
    fprintf(stderr, "HINT: %s\n", fmt);
    return 0; /* return value does not matter */
}

/********* here's the actual test *********/

typedef struct MyStorage {
    PGShmemHeader header;
    int flag;
    PGSemaphoreData sem;
} MyStorage;

int main(int argc, char** argv)
{
    MyStorage* storage = NULL;
    int cpid;

    printf("Creating shared memory ... ");
    fflush(stdout);

    storage = (MyStorage*)PGSharedMemoryCreate(8192, false, 5433);

    storage->flag = 1234;

    printf("OK\n");

    printf("Creating semaphores ... ");
    fflush(stdout);

    PGReserveSemaphores(2, 5433);

    PGSemaphoreCreate(&storage->sem);

    printf("OK\n");

    /* sema initial value is 1, so lock should work */

    printf("Testing Lock ... ");
    fflush(stdout);

    PGSemaphoreLock(&storage->sem, false);

    printf("OK\n");

    /* now sema value is 0, so trylock should fail */

    printf("Testing TryLock ... ");
    fflush(stdout);

    if (PGSemaphoreTryLock(&storage->sem))
        printf("unexpected result!\n");
    else
        printf("OK\n");

    /* unlocking twice and then locking twice should work... */

    printf("Testing Multiple Lock ... ");
    fflush(stdout);

    PGSemaphoreUnlock(&storage->sem);
    PGSemaphoreUnlock(&storage->sem);

    PGSemaphoreLock(&storage->sem, false);
    PGSemaphoreLock(&storage->sem, false);

    printf("OK\n");

    /* check Reset too */

    printf("Testing Reset ... ");
    fflush(stdout);

    PGSemaphoreUnlock(&storage->sem);

    PGSemaphoreReset(&storage->sem);

    if (PGSemaphoreTryLock(&storage->sem))
        printf("unexpected result!\n");
    else
        printf("OK\n");

    /* Fork a child process and see if it can communicate */

    printf("Forking child process ... ");
    fflush(stdout);

    cpid = fork();
    if (cpid == 0) {
        /* In child */
        on_exit_reset();
        sleep(3);
        storage->flag++;
        PGSemaphoreUnlock(&storage->sem);
        proc_exit(0);
    }
    if (cpid < 0) {
        /* Fork failed */
        printf("failed: %s\n", gs_strerror(errno));
        proc_exit(1);
    }

    printf("forked child PID %d OK\n", cpid);

    if (storage->flag != 1234)
        printf("Wrong value found in shared memory!\n");

    printf("Waiting for child (should wait 3 sec here) ... ");
    fflush(stdout);

    PGSemaphoreLock(&storage->sem, false);

    printf("OK\n");

    if (storage->flag != 1235)
        printf("Wrong value found in shared memory!\n");

    /* Test shutdown */

    printf("Running shmem_exit processing ... ");
    fflush(stdout);

    shmem_exit(0);

    printf("OK\n");

    printf("Tests complete.\n");

    proc_exit(0);

    return 0; /* not reached */
}
